#include "StdAfx.h"
#include "Object.h"
#include "ResourceManager.h"
#include <math.h>

Object::Object (Prefab* prefab, Vector2Int position, float velY, StateMain* world): 
m_prefab(prefab), 
m_position(position), 
m_fVelY(velY),
m_block(),
m_world(world),
m_bIsDroppingFast(false)
{
	m_iColor = rand() % 10;
	m_fPosY = (float) position.y;
	SetModel();
}

Object::Object (std::vector<Vector2Int> block, Vector2Int position, float velY, int color, StateMain* world): 
m_prefab(NULL), 
m_position(position), 
m_fVelY(velY),
m_block(block),
m_world(world),
m_iColor(color),
m_bIsDroppingFast(false)
{
	m_fPosY = (float) position.y;
	SetModel();
}

Object::~Object ()
{
	glDeleteBuffers(2, m_boIds);
}

bool Object::Update (float deltaTime)
{
	float posY = m_bIsDroppingFast ? m_fPosY + deltaTime * (m_fVelY + 10.0f) : m_fPosY + deltaTime * m_fVelY;
	if ( m_position.y < (int) ceil(posY) )
	{
		return Move( Vector2Int(0, (int) ceil(posY) - m_position.y) );
	}
	else
	{
		m_fPosY = posY;
		return true;
	}
}

bool Object::Move (Vector2Int distance)
{
	Vector2Int newPosition = m_world->ConvertToWorldPos(m_position + distance);
	std::vector<Vector2Int> block = (m_prefab) ? m_prefab->m_block : m_block;
	if ( m_world->IsAvailableSpace(block, newPosition) )
	{
		m_position = newPosition;
		return true;
	}
	else
	{
		return false;
	}
}

void Object::Rotate ()
{
 	Prefab* rotatePrefab = GetResourceManager()->GetPrefab(m_prefab->m_iRotatePrefabId);
	bool canRotate = rotatePrefab 
		&& m_world->IsAvailableSpace(m_prefab->m_spaceToRotate, m_position)
		&& m_world->IsAvailableSpace(rotatePrefab->m_block, m_position);
	if (canRotate) 
	{
		m_prefab = rotatePrefab;
	}
}

void Object::DropFast (bool isDroppingFast)
{
	m_bIsDroppingFast = isDroppingFast;
}

std::vector<Vector2Int> Object::GetBlock ()
{
	if (m_prefab)
	{
		return m_prefab->m_block;
	}
	else
	{
		return m_block;
	}
}

Vector2Int Object::GetPosition ()
{
	return m_position;
}

void Object::ArrangeBlock ()
{
	// To do: Arrange block to split
}


// Cleared rows split objects into smaller objects
// returnList: a list that is passed to get the new objects
// The function returns a bool to tell whether this object should be delete or not

bool Object::SplitObject (std::vector<Object*>& returnList)
{
	std::vector<Vector2Int> newBlock;
	std::vector<Vector2Int> block = m_prefab ? m_prefab->m_block : m_block;
	std::vector<int> rowToClear = m_world->GetRowToClear();
	
	for (std::vector<Vector2Int>::iterator it=block.begin(); it!=block.end(); it++)
	{
		int row = -1;
		Vector2Int vector;
		for (std::vector<int>::iterator it_row=rowToClear.begin(); it_row!=rowToClear.end(); it_row++)
		{
			vector = m_world->ConvertToWorldPos( (*it) + m_position );
			if ( vector.y == *it_row )
			{
				row = *it_row;
				break;
			}
		}

		if (row < 0)
		{
			newBlock.push_back( vector );
		}
		else if (newBlock.size() > 0)
		{
			Object* newObject = new Object(newBlock, Vector2Int(), 10.0f, m_iColor, m_world);
			returnList.push_back(newObject);
			newBlock.clear();
		}
	}

	if (newBlock.size() > 0)
	{
		m_block = newBlock;
		m_prefab = NULL;
		m_position = Vector2Int();
		m_fPosY = 0.0f;
		returnList.push_back(this);
		return false;
	}
	else
	{
		return true;
	}
}

void Object::SetModel ()
{
	//Model
	Vector3 vertices[8];
	float r1 = 1.0f;
	float r2 = 2.0f;
	float rad = 2.0f * PI / m_world->GetNumCol();

	vertices[0] = Vector3(0.0f, 0.0f, r1);
	vertices[1] = Vector3(0.0f, 1.0f, r1);
	vertices[2] = Vector3(r1 * sin(rad), 1.0f, r1 * cos(rad));
	vertices[3] = Vector3(r1 * sin(rad), 0.0f, r1 * cos(rad));
	vertices[4] = Vector3(0.0f, 0.0f, r2);
	vertices[5] = Vector3(r2 * sin(rad), 0.0f, r2 * cos(rad));
	vertices[6] = Vector3(r2 * sin(rad), 1.0f, r2 * cos(rad));
	vertices[7] = Vector3(0.0f, 1.0f, r2);

	short indices[36];
	indices[0]  = 0; indices[1]  = 1; indices[2]  = 2; indices[3]  = 2; indices[4]  = 3; indices[5]  = 0;
	indices[6]  = 0; indices[7]  = 4; indices[8]  = 7; indices[9]  = 7; indices[10] = 1; indices[11] = 0;
	indices[12] = 7; indices[13] = 4; indices[14] = 5; indices[15] = 5; indices[16] = 6; indices[17] = 7;
	indices[18] = 7; indices[19] = 6; indices[20] = 2; indices[21] = 2;	indices[22] = 1; indices[23] = 7;
	indices[24] = 2; indices[25] = 6; indices[26] = 5; indices[27] = 5;	indices[28] = 3; indices[29] = 2;
	indices[30] = 5; indices[31] = 4; indices[32] = 0; indices[33] = 0;	indices[34] = 3; indices[35] = 5;

	// Push to GPU
	glGenBuffers(2, m_boIds);
	glBindBuffer(GL_ARRAY_BUFFER, m_boIds[0]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_boIds[1]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, 36 * sizeof(short), indices, GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void Object::Render ()
{
	Shaders* shader = Renderer::GetInstance()->GetShader();
	Matrix camMatrix = m_world->GetCamera()->GetMatrix();

	glUseProgram(shader->program);
	glBindBuffer(GL_ARRAY_BUFFER, m_boIds[0]);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_boIds[1]);

	std::vector<Vector2Int> block = m_prefab ? m_prefab->m_block : m_block;
	
	for (std::vector<Vector2Int>::iterator it=block.begin(); it!=block.end(); it++)
	{
		Vector2Int vector = m_world->ConvertToWorldPos( (*it) + m_position );

		// Set matrix
		Matrix scale, rotation, translation;
		scale.SetScale(1.5, 1.0, 1.5);
		rotation.SetRotationY( (vector.x-1) * 2.0f * PI / m_world->GetNumCol() );
		translation.SetTranslation(0.0f, 20 - (*it).y - m_fPosY, 0.0f);
		Matrix m = scale * rotation * translation * camMatrix;

		// Push matrix
		if (shader->matrixUniform != -1)
			glUniformMatrix4fv(shader->matrixUniform, 1, GL_FALSE, (GLfloat*)m.m); 

		// Push color
		Vector3 color(m_iColor / 10.0f ,(m_iColor%5) / 10.0f, (m_iColor % 3) / 10.0f);
		if (shader->colorUniform != -1)
			glUniform3f(shader->colorUniform, color.x, color.y, color.z);

		// Push info into the shader
		if (shader->positionAttribute != -1)
		{
			glEnableVertexAttribArray(shader->positionAttribute);
			glVertexAttribPointer(shader->positionAttribute, 3, GL_FLOAT, GL_FALSE, sizeof(Vector3), 0);
		}
		
		glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, 0);

		// Draw the outline
		if (shader->colorUniform != -1)
				glUniform3f(shader->colorUniform, 0, 0, 0);
		glDrawElements(GL_LINES, 36, GL_UNSIGNED_SHORT, 0);
	}

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}